package pro.cherish.smartfile.rest.upload;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.web.bind.annotation.*;
import pro.cherish.smartfile.common.enums.biz.ChunkStatusEnum;
import pro.cherish.smartfile.common.enums.biz.TaskStatusEnum;
import pro.cherish.smartfile.common.exception.BusinessException;
import pro.cherish.smartfile.common.exception.SmartFileRetryException;
import pro.cherish.smartfile.common.response.Result;
import pro.cherish.smartfile.component.FastUploadHelper;
import pro.cherish.smartfile.component.FileManager;
import pro.cherish.smartfile.component.RedisFileHelper;
import pro.cherish.smartfile.config.properties.SysProperties;
import pro.cherish.smartfile.module.dto.TaskDto;
import pro.cherish.smartfile.module.entity.Attachment;
import pro.cherish.smartfile.module.entity.AttachmentChunkTemp;
import pro.cherish.smartfile.module.req.FileUploadReq;
import pro.cherish.smartfile.module.req.StartUploadReq;
import pro.cherish.smartfile.service.AttachmentChunkTempService;
import pro.cherish.smartfile.service.AttachmentService;
import pro.cherish.smartfile.util.Md5Util;
import reactor.core.publisher.Mono;

import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author 创建人：lys
 * @version 版本号：V1.0
 * <p>
 * ***************************修订记录************************************
 * 2020-12-01 lys 创建该类功能。
 * **********************************************************************
 * </p>
 * @ClassName 类名：IndexController
 * @Description 功能说明：
 * <p>
 *
 * </p>
 * **********************************************************************
 * @date 创建日期： 2020-12-01 lys
 */

@RestController
@RequestMapping("/api/v1/file")
@Slf4j
@Api(value = "文件上传/下载")
public class FileUploadController {


    @Autowired
    private FileManager fileManager;

    @Autowired
    private SysProperties sysProperties;
    @Autowired
    private AttachmentService attachmentService;
    @Autowired
    private AttachmentChunkTempService attachmentChunkTempService;

    @CrossOrigin(origins = "*", maxAge = 3600)
    @GetMapping(value = "/getUploadParam")
    @ApiOperation(value = "获取上传参数")
    public Mono<Result> getUploadParam() {
        Integer chunkSize = sysProperties.getChunkSize();
        Map<String, Object> result = new HashMap<>(2);
        result.put("chunkSize", chunkSize);
        return Mono.just(Result.ok(result));
    }

    @CrossOrigin(origins = "*", maxAge = 3600)
    @PostMapping(value = "/startFileChunkUpload")
    @ApiOperation(value = "开始文件上传预处理")
    public Mono<Result> startFileChunkUpload(@RequestBody @Valid StartUploadReq startUploadReq) {
        return fileManager.startFileChunkUploadPreHandle(startUploadReq);
    }

    @CrossOrigin(origins = "*", maxAge = 3600)
    @ApiOperation("检测某个任务的某个分块文件的上传情况")
    @GetMapping(value = "/check-one-chunk")
    public Mono<Result> check(@RequestParam String taskId, @RequestParam Integer chunk) {
        TaskDto taskDto = RedisFileHelper.getInstance().getTaskById(taskId);
        boolean result = fileManager.chunkMd5CheckPass(taskDto, chunk);
        return Mono.just(Result.ok(result));
    }

    @CrossOrigin(origins = "*", maxAge = 3600)
    @ApiOperation("检测某个任务的归档文件情况")
    @GetMapping(value = "/checkArchiveMd5")
    public Mono<Result> checkArchiveMd5(@ApiParam("前端文件md5") @RequestParam String taskId) throws IOException, NoSuchAlgorithmException {
        log.info("检测摸个任务的归档文件情况");
        String archiveFile = fileManager.getArchiveFile(taskId);
        if (StrUtil.isNotBlank(archiveFile)) {
            boolean result = fileManager.archiveMd5CheckPass(taskId, new File(archiveFile));
            return Mono.just(Result.ok(result));
        }
        return Mono.just(Result.ok(TaskStatusEnum.FILE_ARCHIVE_FAIL, null));
    }

    @CrossOrigin(origins = "*", maxAge = 3600)
    @GetMapping(value = "/merge")
    public Mono<Result> merge(@RequestParam String taskId) throws IOException {
        Attachment attachment = attachmentService.queryFile(taskId);
        if (ObjectUtil.isNotNull(attachment)) {
            // fixme 姑且认为跳过，各种临时数据已经被清理，无法再合并了
            // 若需严谨则再md5一次归档文件，确保文件成功，否则要求用户覆盖式重传
            return Mono.just(Result.ok(TaskStatusEnum.FILE_ALREADY_ARCHIVE, null));
        }
        fileManager.newMerge(taskId, true);
        return Mono.just(Result.ok(TaskStatusEnum.FILE_ALREADY_ARCHIVE, null));
    }

    /**
     * 查询出已经上传的分片
     *
     * @param fileUploadReq
     * @return
     */
    @CrossOrigin(origins = "*", maxAge = 3600)
    @GetMapping(value = "/upload")
    public Mono<Result> checkExistChunk(FileUploadReq fileUploadReq) {
        String identifier = fileUploadReq.getIdentifier();
        // 秒传：文件存在，则直接直接给地址，告知前端
        // 1)文件已经入库
        Attachment archive = FastUploadHelper.getInstance().getArchiveWithDb(identifier);
        boolean archiveFileMd5Ok = false;
        if (ObjectUtil.isNotNull(archive)) {
            // 2)文件存在
            String fileMd5 = archive.getFileMd5();
            String archiveFilePath = fileManager.getArchiveFile(fileMd5);
            if (StrUtil.isNotBlank(archiveFilePath)) {
                archiveFileMd5Ok = fileManager.archiveMd5CheckPass(fileMd5, new File(archiveFilePath));
            }
        }
        int status = checkSomeThing(fileUploadReq);
        if (status == 0) {
            throw new BusinessException(TaskStatusEnum.MUST_RESTART.getCode(), TaskStatusEnum.MUST_RESTART.getMsg());
        }
        // 3)MD5值正确
        Map<String, Object> result = new HashMap<>(2);
        if (archiveFileMd5Ok) {
            result.put("skip", true);
            result.put("path", "暂时未实现");
        } else {
            result.put("skip", false);
            List<Integer> realExistChunks = fileManager.getExistChunkNums(fileUploadReq.getIdentifier());
            result.put("realExistChunks", realExistChunks);
        }
        return Mono.just(Result.ok(result));
    }

    /**
     * 检测部分信息，例如分块大小，变更则返回特定标志位处理
     *
     * @param fileUploadReq
     * @return
     */
    private int checkSomeThing(FileUploadReq fileUploadReq) {
        String identifier = fileUploadReq.getIdentifier();
        AttachmentChunkTemp attachmentChunkTemp = attachmentChunkTempService.queryByFileId(identifier);
        if (ObjectUtil.isNull(attachmentChunkTemp)) {
            log.error("AttachmentChunkTemp 丢失了");
        }
        Integer needSize = attachmentChunkTemp.getChunkSize();
        Integer needLastSize = attachmentChunkTemp.getLastChunkSize();
        if (fileUploadReq.getChunkNumber().equals(fileUploadReq.getTotalChunks())) {
            // 最后一片
            if (!fileUploadReq.getCurrentChunkSize().equals(needLastSize)) {
                // 非正常分块传输
                return 0;
            }
        } else if (fileUploadReq.getChunkNumber() < fileUploadReq.getTotalChunks()) {
            // 正常分片
            if (!fileUploadReq.getCurrentChunkSize().equals(needSize)) {
                // 非正常分块传输
                return 0;
            }
        }
        return 1;
    }


    @CrossOrigin(origins = "*", maxAge = 3600)
    @PostMapping(value = "/upload")
    public Mono<Result> upload(FileUploadReq fileUploadReq, @RequestPart("file") FilePart filePart) {
        // 快传逻辑
        String identifier = fileUploadReq.getIdentifier();
        // fixme 应该是不会进入的，文件存在，早已归档，不该上传，应该走"快传"逻辑，直接返回
        Attachment attachment = attachmentService.queryFile(identifier);
        if (ObjectUtil.isNotNull(attachment)) {
            log.info("fixme 此次应该是不会进入的，文件存在,不应该再来上来");
            return Mono.just(Result.ok(TaskStatusEnum.FILE_ALREADY_ARCHIVE, null));
        }
        AtomicReference<ByteBuffer> byteBufferRef = new AtomicReference<>();
        filePart.content().subscribe(buffer -> {
            byteBufferRef.set(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
        });
        //获取request body
        ByteBuffer byteBuffer = byteBufferRef.get();
        byte[] bytes = byteBuffer.array();
        String currentChunkMd5 = fileUploadReq.getCurrentChunkMd5();
        String calculateMd5Str = null;
        try {
            calculateMd5Str = Md5Util.byteArray2Md5(bytes);
        } catch (NoSuchAlgorithmException e) {
            log.error("字节计算md5失败");
        }
        if (StrUtil.equals(currentChunkMd5, calculateMd5Str)) {
            fileManager.upload2(fileUploadReq, filePart);
            return Mono.just(Result.ok(ChunkStatusEnum.UP_OK, null));
        } else {
            // 字节网络传输导致丢失了，根据特定前端交互方式进行相应方法
            throw new SmartFileRetryException("网络环境差，文件内容丢失了");
        }
    }


}
